.. _глава_83_start:

static foreach
==============

.. index::
    static foreach

Ранее в главе :ref:`Кортежи`<глава_77_start>` мы рассматривали foreach времени компиляции.
foreach времени компиляции выполняет итерацию цикла во время компиляции и разворачивает каждую итерацию как
отдельные фрагменты кода. Например, в следующем цикле foreach над кортежем:

.. code-block:: d

    auto t = tuple(42, "hello", 1.5);
    foreach (i, member; t)
    {
        writefln("%s: %s", i, member);
    }

Компилятор разворачивает цикл так, как показано ниже:

.. code-block:: d

    {
        enum size_t i = 0;
        int member = t[i];
        writefln("%s: %s", i, member);
    }
    {
        enum size_t i = 1;
        string member = t[i];
        writefln("%s: %s", i, member);
    }
    {
        enum size_t i = 2;
        double member = t[i];
        writefln("%s: %s", i, member);
    }

Несмотря на свою мощь, некоторые свойства foreach времени компиляции могут оказаться непригодными в некоторых случаях:
* При компилируемом foreach каждое разворачивание цикла вводит область видимости. Как
как показано выше на примере переменных i и member, это позволяет использовать символ в
более чем одной области видимости, не вызывая ошибки множественного определения. Хотя
это может быть желательно в некоторых случаях, это делает невозможным для кода, развёрнутого
для одной итерации, чтобы использовать код из других итераций.
* Компилируемый foreach работает только с кортежами (включая аргументы шаблона
в виде AliasSeq). Например, несмотря на то, что элементы следующего
литерала массива известны во время компиляции, цикл всегда будет выполняться во время исполнения
(это вполне может быть желаемым поведением):

.. code-block:: d

    void main()
    {
        enum arr = [1, 2];
        // Executed at run time, not unrolled at compile time:
        foreach (i; arr)
        {
            // ...
        }
    }

Как и обычный foreach, foreach времени компиляции может быть использован только внутри функции.
Например, его нельзя использовать в области видимости модуля или внутри пользовательского определения типа.

.. code-block:: d

    import std.meta;

    // Attempting to define function overloads at module scope:
    foreach (T; AliasSeq!(int, double))     { // ← ошибка компиляции
        T twoTimes(T arg)
        {
            return arg * 2;
        }
    }
    void main()
    {
    }


.. code-block:: console

    Error: declaration expected, not foreach


При использовании foreach времени компиляции может быть неясно, должны ли операторы break и continue внутри тела цикла должны влиять на саму итерацию цикла во время компиляции
или же они должны быть частью развёрнутого кода. Статический foreach - это более мощная функция времени компиляции, которая обеспечивает больший
контроль:
* static foreach может работать с любым диапазоном элементов, который может быть вычислен
во время компиляции (включая диапазоны чисел, например 1...10). Например, если задан
диапазон FibonacciSeries из главы :ref:`Диапазоны<глава_81_start>` и функцию
которая определяет, является ли число четным:

.. code-block:: d

    static foreach (n; FibonacciSeries().take(10).filter!isEven)
    {
        writeln(n);
    }

Приведенный выше цикл будет развернут в следующий эквивалент:

.. code-block:: d

    writeln(0);
    writeln(2);
    writeln(8);
    writeln(34);

static foreach можно использовать в области видимости модуля.
∙static foreach не вводит отдельную область видимости для каждой итерации. Для
например, следующий цикл определяет две перегрузки функции в области видимости модуля
scope:

.. code-block:: d

    import std.meta;

    static foreach (T; AliasSeq!(int, double))
    {
        T twoTimes(T arg)
        {
            return arg * 2;
        }
    }
    void main()
    {
    }

Приведенный выше цикл будет развернут в следующий эквивалент:

.. code-block::

    int twoTimes(int arg)
    {
        return arg * 2;
    }

    double twoTimes(double arg)
    {
        return arg * 2;
    }

Операторы break и continue внутри статического цикла foreach нуждаются в метках
для наглядности. Например, следующий код разворачивает (генерирует) предложения case
внутри оператора switch. Выражения break, которые находятся под каждым выражением case
должны упоминать метками связанные с ними операторы switch:

.. code-block:: d

    import std.stdio;

    void main(string[] args)
    {
    theSwitchStatement:
        switch (args.length)
        {
            static foreach (i; 1 .. 3)
            {
        case i:
                writeln(i);
                break theSwitchStatement;
            }
        default:
            writeln("default case");
            break;
        }
    }

После разворачивания приведенного выше цикла оператор switch будет эквивалентен
следующему коду:

.. code-block:: d

    switch (args.length)
    {
    case 1 : writeln(1);
        break;
    case 2 : writeln(2);
        break;
    default : writeln("default case");
        break;
    }

